package com.shigen.demo.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.PageUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shigen.demo.Enum.HttpStatusCode;
import com.shigen.demo.bean.PageRequest;
import com.shigen.demo.bean.Result;
import com.shigen.demo.config.ConfigProperty;
import com.shigen.demo.entity.LoginLog;
import com.shigen.demo.entity.User;
import com.shigen.demo.entity.UserInfo;
import com.shigen.demo.entity.req.UserReq;
import com.shigen.demo.mapper.LoginLogMapper;
import com.shigen.demo.mapper.UserInfoMapper;
import com.shigen.demo.mapper.UserMapper;
import com.shigen.demo.service.IUserService;
import com.shigen.demo.util.Common.Redis;
import com.shigen.demo.util.IPUtil;
import com.shigen.demo.util.RedisUtil;
import com.shigen.demo.util.TokenUtil;
import com.shigen.demo.util.UserAgentUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

  @Resource
  private LoginLogMapper loginLogMapper;
  @Resource
  private RedisUtil redisUtil;
  @Resource
  private UserInfoMapper userInfoMapper;
  @Resource
  private ConfigProperty configProperty;
  @Resource
  private TokenUtil tokenUtil;

  @Transactional
  public Result Login(UserReq.Login login, HttpServletRequest request) {
    String failedCountKey = Redis.LOGIN_FAILED_COUNT_PREFIX + login.getId();
    String onlineInfoKey = Redis.ONLINE_INFO_PREFIX + login.getId();
    // 初始化登录的记录
    Integer failedCount = redisUtil.getIntVal(failedCountKey);
    if (failedCount == null) {
      redisUtil.expire(failedCountKey, 0, configProperty.getFailDuration());
      failedCount = 0;
    }
    // 没有超过试错的次数
    if (failedCount < configProperty.getAllowFailTime()) {
      User userInTable = lambdaQuery().eq(User::getId, login.getId()).eq(User::getPassword, login.getPassword()).eq(User::getIsDel, false).one();

      //记录日志信息
      String ip = IPUtil.getIp(request);
      if (ip == null) {
        log.error("time:{} userid: {} 获取ip失败", DateUtil.now(), login.getId());
        return Result.err("获取登录信息失败");
      }
      String cityInfo = null;
      try {
        cityInfo = IPUtil.getCityInfo(ip);
      } catch (IOException e) {
        e.printStackTrace();
        log.error("time:{} ip:{} 解析失败", DateUtil.now(), ip);
        return Result.err("服务器内部错误");
      }

      LoginLog loginLog = new LoginLog()
          .setId(login.getId())
          .setMurmur(login.getMurmur())
          .setIpAddr(ip)
          .setLocation(cityInfo);
      // 处理浏览器和操作系统
      try {
        String userAgent = UserAgentUtil.getUserAgent(request);
        String browser = UserAgentUtil.getBorderName(userAgent) + ' ' + UserAgentUtil.getBorderType(userAgent) + ' ' + UserAgentUtil.getBrowserVersion(userAgent);
        String os = UserAgentUtil.getDeviceType(userAgent) + ' ' + UserAgentUtil.getOs(userAgent) + ' ' + UserAgentUtil.getOsVersion(userAgent);

        loginLog.setBrowser(browser);
        loginLog.setOs(os);
      } catch (Exception e) {
        log.error("浏览器和操作系统获取失败，exception: {}", e.getMessage());
      }
      Boolean status = (userInTable == null);
      loginLog.setStatus(status);
      loginLog.setLoginTime(DateUtil.date());
      loginLogMapper.insert(loginLog);

      // 登录失败
      if (userInTable == null) {
        failedCount++;
        redisUtil.expire(failedCountKey, failedCount, configProperty.getFailDuration());
        return Result.err("账号或者密码错误");
      } else {
        // 密码验证正确，确认是否在其它设备登陆
        Map<Object, Object> onlineInfo = redisUtil.hGetAll(onlineInfoKey);
        // 验证是否可以多台设备登录
        boolean allowMultipleDevices = Boolean.parseBoolean(configProperty.getAllowMultipleDevices());

        if (!onlineInfo.isEmpty() && !allowMultipleDevices) {
          log.info("onlineInfo: {} allowMultipleDevices: {}", onlineInfo.toString(), false);
          return Result.err("账号在 " + onlineInfo.get("loginTime") + "  于 " + onlineInfo.get("addr") + " 已登录！");
        } else {
          Map<String, String> onlineInfo1 = new HashMap<>();
          onlineInfo1.put("id", String.valueOf(userInTable.getId()));
          onlineInfo1.put("addr", cityInfo);
          onlineInfo1.put("loginTime", DateUtil.now());
          redisUtil.hPutAll(onlineInfoKey, onlineInfo1, configProperty.getTokenTimeOut());

          // 生成token 并查表，存储用户的信息
          String token = tokenUtil.getToken();
          String tokenKey = Redis.TOKEN_PREFIX + token;

          UserInfo userInfo = userInfoMapper.selectOne(new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getUserId, login.getId()));

          redisUtil.expire(tokenKey, JSONObject.toJSONString(userInfo), configProperty.getTokenTimeOut());

          JSONObject jsonObject = new JSONObject();
          jsonObject.put("userid", userInTable.getId());
          jsonObject.put("token", token);
          return Result.ok(jsonObject, "登录成功");
        }
      }
    } else {
      Long expire = redisUtil.getExpire(failedCountKey);
      String prompt = "";
      if (expire != null && expire > 0) {
        prompt = expire / 60 > 0 ? expire / 60 + " 分钟" : expire % 60 + " 秒";
      }
      String msg = "账号曾多次登录失败，请 " + prompt + "之后重试";
      return Result.err(msg);
    }
  }

  @Transactional
  public Result Logout(String token, Integer userid) {
    if (userid == null) {
      return new Result(HttpStatusCode.BAD_REQUEST);
    }
    String onlineInfoKey = Redis.ONLINE_INFO_PREFIX + userid;

    // 解析加密的token 获得真实的token
    String realToken = tokenUtil.getRealToken(token);

    String tokenKey = Redis.TOKEN_PREFIX + realToken;
    redisUtil.delete(onlineInfoKey);
    redisUtil.delete(tokenKey);
    return Result.ok("已注销登录");
  }

  public JSONObject getOnlineUser(PageRequest page) {
    int pagenum = (int) page.getPagenum();
    // hutool起始页计算从 1开始 {"totalPage":1,"start":10,"end":1}
    pagenum = pagenum == 0 ? pagenum : pagenum - 1;
    int pagesize = (int) page.getPagesize();

    // hutool的彩虹分页
    int start = PageUtil.getStart(pagenum, pagesize);
    int end = PageUtil.getEnd(pagenum, pagesize);

    // 存储登录的信息
    List<Map<Object, Object>> onlineInfos = new ArrayList<>();

    // 获得所有的token
    Set<String> keys1 = redisUtil.keys(Redis.ONLINE_INFO_PREFIX + "*");
    if (CollectionUtil.isNotEmpty(keys1)) {
      // 遍历token的set拿到对应的登录的信息
      keys1.forEach(key -> onlineInfos.add(redisUtil.hGetAll(key)));

      // 对登录的信息按照登录的时间降序排序
      onlineInfos.sort(new Comparator<Map<Object, Object>>() {
        @Override
        public int compare(Map<Object, Object> o1, Map<Object, Object> o2) {
          return o1.get("loginTime").toString().compareTo(o2.get("loginTime").toString());
        }
      });
    }

    int size = onlineInfos.size();
    int totalPage = PageUtil.totalPage(size, pagesize);

    // [0,leength) subList java.lang.IndexOutOfBoundsException: toIndex = 20
    if (end > size) {
      end = size;
    }

    JSONObject result = new JSONObject();
    result.put("size", pagesize);
    result.put("current", pagenum == 0 ? pagenum + 1 : pagenum);
    result.put("total", size);
    result.put("pages", totalPage);
    if (CollectionUtil.isNotEmpty(onlineInfos)) {
      result.put("records", onlineInfos.subList(start, end));
    }
    return result;
  }

  @Override
  public Result forceLogOut(String token, Integer userid) {
    return Logout(token, userid);
  }

  @Override
  public long getOnlineUserCount() {
    return redisUtil.keys(Redis.ONLINE_INFO_PREFIX + "*").size();
  }
}

